// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/embedded_worker_instance.h"

#include <utility>

#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/non_thread_safe.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/devtools/service_worker_devtools_manager.h"
#include "content/browser/service_worker/embedded_worker_registry.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/common/content_switches_internal.h"
#include "content/common/service_worker/embedded_worker_messages.h"
#include "content/common/service_worker/embedded_worker_settings.h"
#include "content/common/service_worker/embedded_worker_start_params.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_switches.h"
#include "ipc/ipc_message.h"
#include "third_party/WebKit/public/web/WebConsoleMessage.h"
#include "url/gurl.h"

namespace content {

namespace {

    // When a service worker version's failure count exceeds
    // |kMaxSameProcessFailureCount|, the embedded worker is forced to start in a
    // new process.
    const int kMaxSameProcessFailureCount = 2;

    const char kServiceWorkerTerminationCanceledMesage[] = "Service Worker termination by a timeout timer was canceled because "
                                                           "DevTools is attached.";

    void NotifyWorkerReadyForInspectionOnUI(int worker_process_id,
        int worker_route_id)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        ServiceWorkerDevToolsManager::GetInstance()->WorkerReadyForInspection(
            worker_process_id, worker_route_id);
    }

    void NotifyWorkerDestroyedOnUI(int worker_process_id, int worker_route_id)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        ServiceWorkerDevToolsManager::GetInstance()->WorkerDestroyed(
            worker_process_id, worker_route_id);
    }

    void NotifyWorkerVersionInstalledOnUI(int worker_process_id,
        int worker_route_id)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        ServiceWorkerDevToolsManager::GetInstance()->WorkerVersionInstalled(
            worker_process_id, worker_route_id);
    }

    void NotifyWorkerVersionDoomedOnUI(int worker_process_id, int worker_route_id)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        ServiceWorkerDevToolsManager::GetInstance()->WorkerVersionDoomed(
            worker_process_id, worker_route_id);
    }

    void SetupOnUI(
        int process_id,
        const ServiceWorkerContextCore* service_worker_context,
        const base::WeakPtr<ServiceWorkerContextCore>& service_worker_context_weak,
        int64_t service_worker_version_id,
        const GURL& url,
        const GURL& scope,
        bool is_installed,
        mojom::EmbeddedWorkerInstanceClientRequest request,
        const base::Callback<void(int worker_devtools_agent_route_id,
            bool wait_for_debugger)>& callback)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::UI);
        int worker_devtools_agent_route_id = MSG_ROUTING_NONE;
        bool wait_for_debugger = false;
        if (RenderProcessHost* rph = RenderProcessHost::FromID(process_id)) {
            // |rph| may be NULL in unit tests.
            worker_devtools_agent_route_id = rph->GetNextRoutingID();
            wait_for_debugger = ServiceWorkerDevToolsManager::GetInstance()->WorkerCreated(
                process_id, worker_devtools_agent_route_id,
                ServiceWorkerDevToolsManager::ServiceWorkerIdentifier(
                    service_worker_context, service_worker_context_weak,
                    service_worker_version_id, url, scope),
                is_installed);
            if (request.is_pending())
                rph->GetRemoteInterfaces()->GetInterface(std::move(request));
        }
        BrowserThread::PostTask(
            BrowserThread::IO,
            FROM_HERE,
            base::Bind(callback, worker_devtools_agent_route_id, wait_for_debugger));
    }

    void CallDetach(EmbeddedWorkerInstance* instance)
    {
        // This could be called on the UI thread if |client_| still be valid when the
        // message loop on the UI thread gets destructed.
        // TODO(shimazu): Remove this after https://crbug.com/604762 is fixed
        if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
            DCHECK(ServiceWorkerUtils::IsMojoForServiceWorkerEnabled());
            return;
        }
        instance->Detach();
    }

    bool HasSentStartWorker(EmbeddedWorkerInstance::StartingPhase phase)
    {
        switch (phase) {
        case EmbeddedWorkerInstance::NOT_STARTING:
        case EmbeddedWorkerInstance::ALLOCATING_PROCESS:
        case EmbeddedWorkerInstance::REGISTERING_TO_DEVTOOLS:
            return false;
        case EmbeddedWorkerInstance::SENT_START_WORKER:
        case EmbeddedWorkerInstance::SCRIPT_DOWNLOADING:
        case EmbeddedWorkerInstance::SCRIPT_READ_STARTED:
        case EmbeddedWorkerInstance::SCRIPT_READ_FINISHED:
        case EmbeddedWorkerInstance::SCRIPT_LOADED:
        case EmbeddedWorkerInstance::SCRIPT_EVALUATED:
        case EmbeddedWorkerInstance::THREAD_STARTED:
            return true;
        case EmbeddedWorkerInstance::STARTING_PHASE_MAX_VALUE:
            NOTREACHED();
        }
        return false;
    }

} // namespace

// Lives on IO thread, proxies notifications to DevToolsManager that lives on
// UI thread. Owned by EmbeddedWorkerInstance.
class EmbeddedWorkerInstance::DevToolsProxy : public base::NonThreadSafe {
public:
    DevToolsProxy(int process_id, int agent_route_id)
        : process_id_(process_id)
        , agent_route_id_(agent_route_id)
    {
    }

    ~DevToolsProxy()
    {
        BrowserThread::PostTask(
            BrowserThread::UI,
            FROM_HERE,
            base::Bind(NotifyWorkerDestroyedOnUI,
                process_id_, agent_route_id_));
    }

    void NotifyWorkerReadyForInspection()
    {
        DCHECK(CalledOnValidThread());
        BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
            base::Bind(NotifyWorkerReadyForInspectionOnUI,
                process_id_, agent_route_id_));
    }

    void NotifyWorkerVersionInstalled()
    {
        DCHECK(CalledOnValidThread());
        BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
            base::Bind(NotifyWorkerVersionInstalledOnUI,
                process_id_, agent_route_id_));
    }

    void NotifyWorkerVersionDoomed()
    {
        DCHECK(CalledOnValidThread());
        BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
            base::Bind(NotifyWorkerVersionDoomedOnUI,
                process_id_, agent_route_id_));
    }

    bool ShouldNotifyWorkerStopIgnored() const
    {
        return !worker_stop_ignored_notified_;
    }

    void WorkerStopIgnoredNotified() { worker_stop_ignored_notified_ = true; }

    int agent_route_id() const { return agent_route_id_; }

private:
    const int process_id_;
    const int agent_route_id_;
    bool worker_stop_ignored_notified_ = false;
    DISALLOW_COPY_AND_ASSIGN(DevToolsProxy);
};

// A handle for a worker process managed by ServiceWorkerProcessManager on the
// UI thread.
class EmbeddedWorkerInstance::WorkerProcessHandle {
public:
    WorkerProcessHandle(const base::WeakPtr<ServiceWorkerContextCore>& context,
        int embedded_worker_id,
        int process_id,
        bool is_new_process)
        : context_(context)
        , embedded_worker_id_(embedded_worker_id)
        , process_id_(process_id)
        , is_new_process_(is_new_process)
    {
        DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id_);
    }

    ~WorkerProcessHandle()
    {
        if (context_)
            context_->process_manager()->ReleaseWorkerProcess(embedded_worker_id_);
    }

    int process_id() const { return process_id_; }
    bool is_new_process() const { return is_new_process_; }

private:
    base::WeakPtr<ServiceWorkerContextCore> context_;

    const int embedded_worker_id_;
    const int process_id_;
    const bool is_new_process_;

    DISALLOW_COPY_AND_ASSIGN(WorkerProcessHandle);
};

// A task to allocate a worker process and to send a start worker message. This
// is created on EmbeddedWorkerInstance::Start(), owned by the instance and
// destroyed on EmbeddedWorkerInstance::OnScriptEvaluated().
// We can abort starting worker by destroying this task anytime during the
// sequence.
class EmbeddedWorkerInstance::StartTask {
public:
    enum class ProcessAllocationState { NOT_ALLOCATED,
        ALLOCATING,
        ALLOCATED };

    StartTask(EmbeddedWorkerInstance* instance,
        const GURL& script_url,
        mojom::EmbeddedWorkerInstanceClientRequest request)
        : instance_(instance)
        , request_(std::move(request))
        , state_(ProcessAllocationState::NOT_ALLOCATED)
        , is_installed_(false)
        , started_during_browser_startup_(false)
        , weak_factory_(this)
    {
        TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker", "EmbeddedWorkerInstance::Start",
            this, "Script", script_url.spec());
    }

    ~StartTask()
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        TRACE_EVENT_ASYNC_END0("ServiceWorker", "EmbeddedWorkerInstance::Start",
            this);

        if (!instance_->context_)
            return;

        switch (state_) {
        case ProcessAllocationState::NOT_ALLOCATED:
            // Not necessary to release a process.
            break;
        case ProcessAllocationState::ALLOCATING:
            // Abort half-baked process allocation on the UI thread.
            instance_->context_->process_manager()->ReleaseWorkerProcess(
                instance_->embedded_worker_id());
            break;
        case ProcessAllocationState::ALLOCATED:
            // Otherwise, the process will be released by EmbeddedWorkerInstance.
            break;
        }

        // Don't have to abort |start_callback_| here. The caller of
        // EmbeddedWorkerInstance::Start(), that is, ServiceWorkerVersion does not
        // expect it when the start worker sequence is canceled by Stop() because
        // the callback, ServiceWorkerVersion::OnStartSentAndScriptEvaluated(),
        // could drain valid start requests queued in the version. After the worker
        // is stopped, the version attempts to restart the worker if there are
        // requests in the queue. See ServiceWorkerVersion::OnStoppedInternal() for
        // details.
        // TODO(nhiroki): Reconsider this bizarre layering.
    }

    void Start(std::unique_ptr<EmbeddedWorkerStartParams> params,
        const StatusCallback& callback)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        state_ = ProcessAllocationState::ALLOCATING;
        start_callback_ = callback;
        is_installed_ = params->is_installed;

        if (!GetContentClient()->browser()->IsBrowserStartupComplete())
            started_during_browser_startup_ = true;

        GURL scope(params->scope);
        GURL script_url(params->script_url);

        bool can_use_existing_process = instance_->context_->GetVersionFailureCount(
                                            params->service_worker_version_id)
            < kMaxSameProcessFailureCount;
        instance_->context_->process_manager()->AllocateWorkerProcess(
            instance_->embedded_worker_id_, scope, script_url,
            can_use_existing_process,
            base::Bind(&StartTask::OnProcessAllocated, weak_factory_.GetWeakPtr(),
                base::Passed(&params)));
    }

    static void RunStartCallback(StartTask* task,
        ServiceWorkerStatusCode status)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        StatusCallback callback = task->start_callback_;
        task->start_callback_.Reset();
        callback.Run(status);
        // |task| may be destroyed.
    }

    bool is_installed() const { return is_installed_; }

private:
    void OnProcessAllocated(std::unique_ptr<EmbeddedWorkerStartParams> params,
        ServiceWorkerStatusCode status,
        int process_id,
        bool is_new_process,
        const EmbeddedWorkerSettings& settings)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);

        if (status != SERVICE_WORKER_OK) {
            TRACE_EVENT_ASYNC_STEP_PAST1(
                "ServiceWorker", "EmbeddedWorkerInstance::Start", this,
                "OnProcessAllocated", "Error", ServiceWorkerStatusToString(status));
            DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, process_id);
            StatusCallback callback = start_callback_;
            start_callback_.Reset();
            instance_->OnStartFailed(callback, status);
            // |this| may be destroyed.
            return;
        }

        TRACE_EVENT_ASYNC_STEP_PAST1(
            "ServiceWorker", "EmbeddedWorkerInstance::Start", this,
            "OnProcessAllocated", "Is New Process", is_new_process);
        if (is_installed_)
            ServiceWorkerMetrics::RecordProcessCreated(is_new_process);

        ServiceWorkerMetrics::StartSituation start_situation = ServiceWorkerMetrics::StartSituation::UNKNOWN;
        if (started_during_browser_startup_)
            start_situation = ServiceWorkerMetrics::StartSituation::DURING_STARTUP;
        else if (is_new_process)
            start_situation = ServiceWorkerMetrics::StartSituation::NEW_PROCESS;
        else
            start_situation = ServiceWorkerMetrics::StartSituation::EXISTING_PROCESS;

        // Notify the instance that a process is allocated.
        state_ = ProcessAllocationState::ALLOCATED;
        instance_->OnProcessAllocated(
            base::MakeUnique<WorkerProcessHandle>(instance_->context_,
                instance_->embedded_worker_id(),
                process_id, is_new_process),
            start_situation);

        // TODO(bengr): Support changes to this setting while the worker
        // is running.
        params->settings.data_saver_enabled = settings.data_saver_enabled;

        // Register the instance to DevToolsManager on UI thread.
        const int64_t service_worker_version_id = params->service_worker_version_id;
        const GURL& scope = params->scope;
        GURL script_url(params->script_url);
        BrowserThread::PostTask(
            BrowserThread::UI, FROM_HERE,
            base::Bind(&SetupOnUI, process_id, instance_->context_.get(),
                instance_->context_, service_worker_version_id, script_url,
                scope, is_installed_, base::Passed(&request_),
                base::Bind(&StartTask::OnSetupOnUICompleted,
                    weak_factory_.GetWeakPtr(), base::Passed(&params),
                    is_new_process)));
    }

    void OnSetupOnUICompleted(std::unique_ptr<EmbeddedWorkerStartParams> params,
        bool is_new_process,
        int worker_devtools_agent_route_id,
        bool wait_for_debugger)
    {
        DCHECK_CURRENTLY_ON(BrowserThread::IO);
        TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker",
            "EmbeddedWorkerInstance::Start", this,
            "OnSetupOnUICompleted");

        // Notify the instance that it is registered to the devtools manager.
        instance_->OnRegisteredToDevToolsManager(
            is_new_process, worker_devtools_agent_route_id, wait_for_debugger);

        params->worker_devtools_agent_route_id = worker_devtools_agent_route_id;
        params->wait_for_debugger = wait_for_debugger;

        ServiceWorkerStatusCode status = instance_->SendStartWorker(std::move(params));
        if (status != SERVICE_WORKER_OK) {
            StatusCallback callback = start_callback_;
            start_callback_.Reset();
            instance_->OnStartFailed(callback, status);
            // |this| may be destroyed.
        }
    }

    // |instance_| must outlive |this|.
    EmbeddedWorkerInstance* instance_;

    // Ownership is transferred by base::Passed() to a task after process
    // allocation.
    mojom::EmbeddedWorkerInstanceClientRequest request_;

    StatusCallback start_callback_;
    ProcessAllocationState state_;

    // Used for UMA.
    bool is_installed_;
    bool started_during_browser_startup_;

    base::WeakPtrFactory<StartTask> weak_factory_;

    DISALLOW_COPY_AND_ASSIGN(StartTask);
};

bool EmbeddedWorkerInstance::Listener::OnMessageReceived(
    const IPC::Message& message)
{
    return false;
}

EmbeddedWorkerInstance::~EmbeddedWorkerInstance()
{
    DCHECK(status_ == EmbeddedWorkerStatus::STOPPING || status_ == EmbeddedWorkerStatus::STOPPED)
        << static_cast<int>(status_);
    devtools_proxy_.reset();
    if (registry_->GetWorker(embedded_worker_id_))
        registry_->RemoveWorker(process_id(), embedded_worker_id_);
    process_handle_.reset();
}

void EmbeddedWorkerInstance::Start(
    std::unique_ptr<EmbeddedWorkerStartParams> params,
    mojom::ServiceWorkerEventDispatcherRequest dispatcher_request,
    const StatusCallback& callback)
{
    restart_count_++;
    if (!context_) {
        callback.Run(SERVICE_WORKER_ERROR_ABORT);
        // |this| may be destroyed by the callback.
        return;
    }
    DCHECK(status_ == EmbeddedWorkerStatus::STOPPED);

    DCHECK(!params->pause_after_download || !params->is_installed);
    DCHECK_NE(kInvalidServiceWorkerVersionId, params->service_worker_version_id);
    step_time_ = base::TimeTicks::Now();
    status_ = EmbeddedWorkerStatus::STARTING;
    starting_phase_ = ALLOCATING_PROCESS;
    network_accessed_for_script_ = false;
    for (auto& observer : listener_list_)
        observer.OnStarting();

    params->embedded_worker_id = embedded_worker_id_;
    params->worker_devtools_agent_route_id = MSG_ROUTING_NONE;
    params->wait_for_debugger = false;
    params->settings.v8_cache_options = GetV8CacheOptions();

    mojom::EmbeddedWorkerInstanceClientRequest request = mojo::MakeRequest(&client_);
    client_.set_connection_error_handler(
        base::Bind(&CallDetach, base::Unretained(this)));

    pending_dispatcher_request_ = std::move(dispatcher_request);

    inflight_start_task_.reset(
        new StartTask(this, params->script_url, std::move(request)));
    inflight_start_task_->Start(std::move(params), callback);
}

bool EmbeddedWorkerInstance::Stop()
{
    DCHECK(status_ == EmbeddedWorkerStatus::STARTING || status_ == EmbeddedWorkerStatus::RUNNING)
        << static_cast<int>(status_);

    // Abort an inflight start task.
    inflight_start_task_.reset();

    if (ServiceWorkerUtils::IsMojoForServiceWorkerEnabled()) {
        if (status_ == EmbeddedWorkerStatus::STARTING && !HasSentStartWorker(starting_phase())) {
            // Don't send the StopWorker message when the StartWorker message hasn't
            // been sent.
            // TODO(shimazu): Invoke OnStopping/OnStopped after the legacy IPC path is
            // removed.
            OnDetached();
            return false;
        }
        client_->StopWorker(base::Bind(&EmbeddedWorkerRegistry::OnWorkerStopped,
            base::Unretained(registry_.get()),
            process_id(), embedded_worker_id()));
    } else {
        ServiceWorkerStatusCode status = registry_->StopWorker(process_id(), embedded_worker_id_);
        UMA_HISTOGRAM_ENUMERATION("ServiceWorker.SendStopWorker.Status", status,
            SERVICE_WORKER_ERROR_MAX_VALUE);
        // StopWorker could fail if we were starting up and don't have a process
        // yet, or we can no longer communicate with the process. So just detach.
        if (status != SERVICE_WORKER_OK) {
            OnDetached();
            return false;
        }
    }

    status_ = EmbeddedWorkerStatus::STOPPING;
    for (auto& observer : listener_list_)
        observer.OnStopping();
    return true;
}

void EmbeddedWorkerInstance::StopIfIdle()
{
    if (devtools_attached_) {
        if (devtools_proxy_) {
            // Check ShouldNotifyWorkerStopIgnored not to show the same message
            // multiple times in DevTools.
            if (devtools_proxy_->ShouldNotifyWorkerStopIgnored()) {
                AddMessageToConsole(blink::WebConsoleMessage::LevelDebug,
                    kServiceWorkerTerminationCanceledMesage);
                devtools_proxy_->WorkerStopIgnoredNotified();
            }
        }
        return;
    }
    Stop();
}

ServiceWorkerStatusCode EmbeddedWorkerInstance::SendMessage(
    const IPC::Message& message)
{
    DCHECK_NE(kInvalidEmbeddedWorkerThreadId, thread_id_);
    if (status_ != EmbeddedWorkerStatus::RUNNING && status_ != EmbeddedWorkerStatus::STARTING) {
        return SERVICE_WORKER_ERROR_IPC_FAILED;
    }
    return registry_->Send(process_id(),
        new EmbeddedWorkerContextMsg_MessageToWorker(
            thread_id_, embedded_worker_id_, message));
}

void EmbeddedWorkerInstance::ResumeAfterDownload()
{
    if (process_id() == ChildProcessHost::kInvalidUniqueID || status_ != EmbeddedWorkerStatus::STARTING) {
        return;
    }
    DCHECK(client_.is_bound());
    client_->ResumeAfterDownload();
}

EmbeddedWorkerInstance::EmbeddedWorkerInstance(
    base::WeakPtr<ServiceWorkerContextCore> context,
    int embedded_worker_id)
    : context_(context)
    , registry_(context->embedded_worker_registry())
    , embedded_worker_id_(embedded_worker_id)
    , status_(EmbeddedWorkerStatus::STOPPED)
    , starting_phase_(NOT_STARTING)
    , restart_count_(0)
    , thread_id_(kInvalidEmbeddedWorkerThreadId)
    , devtools_attached_(false)
    , network_accessed_for_script_(false)
    , weak_factory_(this)
{
}

void EmbeddedWorkerInstance::OnProcessAllocated(
    std::unique_ptr<WorkerProcessHandle> handle,
    ServiceWorkerMetrics::StartSituation start_situation)
{
    DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_);
    DCHECK(!process_handle_);

    process_handle_ = std::move(handle);
    starting_phase_ = REGISTERING_TO_DEVTOOLS;
    start_situation_ = start_situation;
    for (auto& observer : listener_list_)
        observer.OnProcessAllocated();
}

void EmbeddedWorkerInstance::OnRegisteredToDevToolsManager(
    bool is_new_process,
    int worker_devtools_agent_route_id,
    bool wait_for_debugger)
{
    if (worker_devtools_agent_route_id != MSG_ROUTING_NONE) {
        DCHECK(!devtools_proxy_);
        devtools_proxy_.reset(
            new DevToolsProxy(process_id(), worker_devtools_agent_route_id));
    }
    if (wait_for_debugger) {
        // We don't measure the start time when wait_for_debugger flag is set. So
        // we set the NULL time here.
        step_time_ = base::TimeTicks();
    }
    for (auto& observer : listener_list_)
        observer.OnRegisteredToDevToolsManager();
}

ServiceWorkerStatusCode EmbeddedWorkerInstance::SendStartWorker(
    std::unique_ptr<EmbeddedWorkerStartParams> params)
{
    if (!context_)
        return SERVICE_WORKER_ERROR_ABORT;
    DCHECK(pending_dispatcher_request_.is_pending());
    client_->StartWorker(*params, std::move(pending_dispatcher_request_));
    registry_->BindWorkerToProcess(process_id(), embedded_worker_id());
    TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
        this, "SendStartWorker");
    OnStartWorkerMessageSent();
    return SERVICE_WORKER_OK;
}

void EmbeddedWorkerInstance::OnStartWorkerMessageSent()
{
    if (!step_time_.is_null()) {
        base::TimeDelta duration = UpdateStepTime();
        if (inflight_start_task_->is_installed()) {
            ServiceWorkerMetrics::RecordTimeToSendStartWorker(duration,
                start_situation_);
        }
    }

    starting_phase_ = SENT_START_WORKER;
    for (auto& observer : listener_list_)
        observer.OnStartWorkerMessageSent();
}

void EmbeddedWorkerInstance::OnReadyForInspection()
{
    if (devtools_proxy_)
        devtools_proxy_->NotifyWorkerReadyForInspection();
}

void EmbeddedWorkerInstance::OnScriptReadStarted()
{
    starting_phase_ = SCRIPT_READ_STARTED;
}

void EmbeddedWorkerInstance::OnScriptReadFinished()
{
    starting_phase_ = SCRIPT_READ_FINISHED;
}

void EmbeddedWorkerInstance::OnScriptLoaded()
{
    using LoadSource = ServiceWorkerMetrics::LoadSource;

    if (!inflight_start_task_)
        return;
    LoadSource source;
    if (network_accessed_for_script_) {
        DCHECK(!inflight_start_task_->is_installed());
        source = LoadSource::NETWORK;
    } else if (inflight_start_task_->is_installed()) {
        source = LoadSource::SERVICE_WORKER_STORAGE;
    } else {
        source = LoadSource::HTTP_CACHE;
    }
    TRACE_EVENT_ASYNC_STEP_PAST1(
        "ServiceWorker", "EmbeddedWorkerInstance::Start",
        inflight_start_task_.get(), "OnScriptLoaded", "Source",
        ServiceWorkerMetrics::LoadSourceToString(source));

    if (!step_time_.is_null()) {
        base::TimeDelta duration = UpdateStepTime();
        ServiceWorkerMetrics::RecordTimeToLoad(duration, source, start_situation_);
    }

    starting_phase_ = SCRIPT_LOADED;
    for (auto& observer : listener_list_)
        observer.OnScriptLoaded();
    // |this| may be destroyed by the callback.
}

void EmbeddedWorkerInstance::OnURLJobCreatedForMainScript()
{
    if (!inflight_start_task_)
        return;
    TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
        inflight_start_task_.get(), "OnURLJobCreated");
    if (!step_time_.is_null()) {
        base::TimeDelta duration = UpdateStepTime();
        if (inflight_start_task_->is_installed())
            ServiceWorkerMetrics::RecordTimeToURLJob(duration, start_situation_);
    }
}

void EmbeddedWorkerInstance::OnWorkerVersionInstalled()
{
    if (devtools_proxy_)
        devtools_proxy_->NotifyWorkerVersionInstalled();
}

void EmbeddedWorkerInstance::OnWorkerVersionDoomed()
{
    if (devtools_proxy_)
        devtools_proxy_->NotifyWorkerVersionDoomed();
}

void EmbeddedWorkerInstance::OnThreadStarted(int thread_id)
{
    if (!inflight_start_task_)
        return;
    TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
        inflight_start_task_.get(), "OnThreadStarted");

    starting_phase_ = THREAD_STARTED;
    if (!step_time_.is_null()) {
        base::TimeDelta duration = UpdateStepTime();
        if (inflight_start_task_->is_installed())
            ServiceWorkerMetrics::RecordTimeToStartThread(duration, start_situation_);
    }

    thread_id_ = thread_id;
    for (auto& observer : listener_list_)
        observer.OnThreadStarted();
}

void EmbeddedWorkerInstance::OnScriptLoadFailed()
{
    if (!inflight_start_task_)
        return;
    TRACE_EVENT_ASYNC_STEP_PAST0("ServiceWorker", "EmbeddedWorkerInstance::Start",
        inflight_start_task_.get(),
        "OnScriptLoadFailed");
    for (auto& observer : listener_list_)
        observer.OnScriptLoadFailed();
}

void EmbeddedWorkerInstance::OnScriptEvaluated(bool success)
{
    if (!inflight_start_task_)
        return;
    DCHECK_EQ(EmbeddedWorkerStatus::STARTING, status_);

    TRACE_EVENT_ASYNC_STEP_PAST1("ServiceWorker", "EmbeddedWorkerInstance::Start",
        inflight_start_task_.get(), "OnScriptEvaluated",
        "Success", success);
    starting_phase_ = SCRIPT_EVALUATED;
    if (!step_time_.is_null()) {
        base::TimeDelta duration = UpdateStepTime();
        if (success && inflight_start_task_->is_installed())
            ServiceWorkerMetrics::RecordTimeToEvaluateScript(duration,
                start_situation_);
    }

    base::WeakPtr<EmbeddedWorkerInstance> weak_this = weak_factory_.GetWeakPtr();
    StartTask::RunStartCallback(
        inflight_start_task_.get(),
        success ? SERVICE_WORKER_OK
                : SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED);
    // |this| may be destroyed by the callback.
}

void EmbeddedWorkerInstance::OnStarted()
{
    // Stop is requested before OnStarted is sent back from the worker.
    if (status_ == EmbeddedWorkerStatus::STOPPING)
        return;
    DCHECK(status_ == EmbeddedWorkerStatus::STARTING);
    status_ = EmbeddedWorkerStatus::RUNNING;
    inflight_start_task_.reset();
    for (auto& observer : listener_list_)
        observer.OnStarted();
}

void EmbeddedWorkerInstance::OnStopped()
{
    EmbeddedWorkerStatus old_status = status_;
    ReleaseProcess();
    for (auto& observer : listener_list_)
        observer.OnStopped(old_status);
}

void EmbeddedWorkerInstance::OnDetached()
{
    EmbeddedWorkerStatus old_status = status_;
    ReleaseProcess();
    for (auto& observer : listener_list_)
        observer.OnDetached(old_status);
}

void EmbeddedWorkerInstance::Detach()
{
    registry_->DetachWorker(process_id(), embedded_worker_id());
    OnDetached();
}

base::WeakPtr<EmbeddedWorkerInstance> EmbeddedWorkerInstance::AsWeakPtr()
{
    return weak_factory_.GetWeakPtr();
}

bool EmbeddedWorkerInstance::OnMessageReceived(const IPC::Message& message)
{
    for (auto& listener : listener_list_) {
        if (listener.OnMessageReceived(message))
            return true;
    }
    return false;
}

void EmbeddedWorkerInstance::OnReportException(
    const base::string16& error_message,
    int line_number,
    int column_number,
    const GURL& source_url)
{
    for (auto& observer : listener_list_) {
        observer.OnReportException(error_message, line_number, column_number,
            source_url);
    }
}

void EmbeddedWorkerInstance::OnReportConsoleMessage(
    int source_identifier,
    int message_level,
    const base::string16& message,
    int line_number,
    const GURL& source_url)
{
    for (auto& observer : listener_list_) {
        observer.OnReportConsoleMessage(source_identifier, message_level, message,
            line_number, source_url);
    }
}

int EmbeddedWorkerInstance::process_id() const
{
    if (process_handle_)
        return process_handle_->process_id();
    return ChildProcessHost::kInvalidUniqueID;
}

bool EmbeddedWorkerInstance::is_new_process() const
{
    DCHECK(process_handle_);
    return process_handle_->is_new_process();
}

int EmbeddedWorkerInstance::worker_devtools_agent_route_id() const
{
    if (devtools_proxy_)
        return devtools_proxy_->agent_route_id();
    return MSG_ROUTING_NONE;
}

MessagePortMessageFilter* EmbeddedWorkerInstance::message_port_message_filter()
    const
{
    return registry_->MessagePortMessageFilterForProcess(process_id());
}

void EmbeddedWorkerInstance::AddListener(Listener* listener)
{
    listener_list_.AddObserver(listener);
}

void EmbeddedWorkerInstance::RemoveListener(Listener* listener)
{
    listener_list_.RemoveObserver(listener);
}

void EmbeddedWorkerInstance::OnNetworkAccessedForScriptLoad()
{
    starting_phase_ = SCRIPT_DOWNLOADING;
    network_accessed_for_script_ = true;
}

void EmbeddedWorkerInstance::ReleaseProcess()
{
    // Abort an inflight start task.
    inflight_start_task_.reset();

    client_.reset();
    devtools_proxy_.reset();
    process_handle_.reset();
    status_ = EmbeddedWorkerStatus::STOPPED;
    starting_phase_ = NOT_STARTING;
    thread_id_ = kInvalidEmbeddedWorkerThreadId;
}

void EmbeddedWorkerInstance::OnStartFailed(const StatusCallback& callback,
    ServiceWorkerStatusCode status)
{
    EmbeddedWorkerStatus old_status = status_;
    ReleaseProcess();
    base::WeakPtr<EmbeddedWorkerInstance> weak_this = weak_factory_.GetWeakPtr();
    callback.Run(status);
    if (weak_this && old_status != EmbeddedWorkerStatus::STOPPED) {
        for (auto& observer : weak_this->listener_list_)
            observer.OnStopped(old_status);
    }
}

base::TimeDelta EmbeddedWorkerInstance::UpdateStepTime()
{
    DCHECK_CURRENTLY_ON(BrowserThread::IO);
    DCHECK(!step_time_.is_null());
    base::TimeTicks now = base::TimeTicks::Now();
    base::TimeDelta duration = now - step_time_;
    step_time_ = now;
    return duration;
}

void EmbeddedWorkerInstance::AddMessageToConsole(
    blink::WebConsoleMessage::Level level,
    const std::string& message)
{
    if (status_ != EmbeddedWorkerStatus::RUNNING && status_ != EmbeddedWorkerStatus::STARTING) {
        return;
    }
    DCHECK(client_.is_bound());
    client_->AddMessageToConsole(level, message);
}

// static
std::string EmbeddedWorkerInstance::StatusToString(
    EmbeddedWorkerStatus status)
{
    switch (status) {
    case EmbeddedWorkerStatus::STOPPED:
        return "STOPPED";
    case EmbeddedWorkerStatus::STARTING:
        return "STARTING";
    case EmbeddedWorkerStatus::RUNNING:
        return "RUNNING";
    case EmbeddedWorkerStatus::STOPPING:
        return "STOPPING";
    }
    NOTREACHED() << static_cast<int>(status);
    return std::string();
}

// static
std::string EmbeddedWorkerInstance::StartingPhaseToString(StartingPhase phase)
{
    switch (phase) {
    case NOT_STARTING:
        return "Not in STARTING status";
    case ALLOCATING_PROCESS:
        return "Allocating process";
    case REGISTERING_TO_DEVTOOLS:
        return "Registering to DevTools";
    case SENT_START_WORKER:
        return "Sent StartWorker message to renderer";
    case SCRIPT_DOWNLOADING:
        return "Script downloading";
    case SCRIPT_LOADED:
        return "Script loaded";
    case SCRIPT_EVALUATED:
        return "Script evaluated";
    case THREAD_STARTED:
        return "Thread started";
    case SCRIPT_READ_STARTED:
        return "Script read started";
    case SCRIPT_READ_FINISHED:
        return "Script read finished";
    case STARTING_PHASE_MAX_VALUE:
        NOTREACHED();
    }
    NOTREACHED() << phase;
    return std::string();
}

} // namespace content
